package com.ming.common.liteflow.core.flow;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.ivy.builder.graph.*;
import com.ivy.parser.logicflow.LogicFlow;
import com.ivy.parser.logicflow.LogicFlowGraphEL;
import com.ivy.parser.utils.CmpScriptUtil;
import com.ming.common.beetl.util.BeetlSqlUtil;
import com.ming.common.liteflow.context.IvyLoadCmp;
import com.ming.common.liteflow.core.enums.LiteFlowEnums;
import com.ming.common.liteflow.core.node.NodeInfoWrapper;
import com.ming.common.liteflow.core.node.SpringBeanUtil;
import com.ming.common.util.CommonUtil;
import com.ming.common.util.FileUtil;
import com.ming.common.util.number.NumberUtil;
import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.flow.LiteflowResponse;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.query.LambdaQuery;
import org.springframework.util.ClassUtils;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class LogicFlowExec {

    private LogicFlow logicFlow;

    private Boolean isFuture = true;

    public LogicFlowExec() {
        logicFlow = LogicFlow.NEW();
    }

    public static LogicFlowExec NEW(){
        return new LogicFlowExec();
    }

    public LogicFlowExec json(String json) {
        logicFlow.json(json);
        return this;
    }
    public LogicFlowExec transform() throws Exception {
        logicFlow.transform();
        return this;
    }

    public LogicFlowExec dynamicClassList(List<IvyDynamicClass> list) {
        logicFlow.dynamicClassList(list);
        return this;
    }

    public LogicFlowExec nodeInfoList(List<IvyCmp> list) {
        logicFlow.nodeInfoList(list);
        return this;
    }

    public LogicFlowExec logicFlow(LogicFlow logicFlow){
        this.logicFlow = logicFlow;
        return this;
    }
    public LogicFlowExec flowExecutor(FlowExecutor flowExecutor){
        this.logicFlow.flowExecutor(flowExecutor);
        return this;
    }

    public List<Node> flowNodeList(){
        List<Node> flowNodeList = new ArrayList<>();
        for (LogicFlowGraphEL graphEL : logicFlow.graphELList){
            List<Node> list = graphEL.getList().keySet().stream().collect(Collectors.toList());
            flowNodeList.addAll(list);
        }
        return flowNodeList;
    }

    public List<Node> fallbackList(){
        List<Node> fallbackList = new ArrayList<>();
        for (LogicFlowGraphEL graphEL : logicFlow.graphELList){
            if(CommonUtil.collUtil.isNotEmpty(graphEL.getFallbackList())){
                fallbackList.addAll(graphEL.getFallbackList());
            }
        }
        return fallbackList;
    }

    public LogicFlowExec loadCmp(){
        List<LogicFlowData> flowDataList = logicFlow.flowDataList;
        Set<Long> idSet = new HashSet<>();
        for (LogicFlowData logicFlowData : flowDataList){
            idSet.addAll(logicFlowData.getCmpIdSet());
        }

        if(CollUtil.isNotEmpty(idSet)){
            SQLManager sqlManager = BeetlSqlUtil.NEW().getDefaultSQLManager();

            LambdaQuery<IvyCmp> query = sqlManager.lambdaQuery(IvyCmp.class);
            query.andIn(IvyCmp::getId, idSet);
            List<IvyCmp> cmpList = query.select();

            flowDataList.forEach(m->{
                Set<Long> itemSet = m.getNodes().stream().map(x -> x.getProperties().getId()).collect(Collectors.toSet());
                m.setIvyCmpList(cmpList.stream().filter(y-> itemSet.contains(y.getId())).collect(Collectors.toList()));
                m.setIvyCmpMap(m.getIvyCmpList().stream().collect(Collectors.toMap(IvyCmp::getId,z->z)));
            });
        }
        return this;
    }

    //同步
    public LogicFlowExec resp() {
        this.isFuture = false;
        return this;
    }

    //异步
    public LogicFlowExec future() {
        this.isFuture = true;
        return this;
    }

    public LiteflowResponse execResponse(){
        return execResponse(null);
    }

    public LiteflowResponse execResponse(Object param, Object... contextBeanArray){
        IvyLoadCmp.loadCmp(logicFlow.json);

        if(logicFlow.graphELList.size() == 1){
            LogicFlowGraphEL graphEL = logicFlow.graphELList.get(0);
            List<Node> flowNodeList = graphEL.getList().keySet().stream().collect(Collectors.toList());
            return execResponse(logicFlow.flowExecutor, logicFlow.nodeInfoList,logicFlow.dynamicClassList, flowNodeList, graphEL.getFallbackList(), logicFlow.wrapper.toEL(), param, contextBeanArray);
        }
        List<Node> flowNodeList = new ArrayList<>();
        List<Node> fallbackList = new ArrayList<>();
        for (LogicFlowGraphEL graphEL : logicFlow.graphELList){
            List<Node> list = graphEL.getList().keySet().stream().collect(Collectors.toList());
            flowNodeList.addAll(list);
            if(CommonUtil.collUtil.isNotEmpty(graphEL.getFallbackList())){
                fallbackList.addAll(graphEL.getFallbackList());
            }
        }
        return execResponse(logicFlow.flowExecutor, logicFlow.nodeInfoList,logicFlow.dynamicClassList, flowNodeList, fallbackList, logicFlow.wrapper.toEL(), param, contextBeanArray);
    }

    public String exec(){
        return exec(null);
    }

    private String exec(Object param, Object... contextBeanArray){
        IvyLoadCmp.loadCmp(this);

        if(logicFlow.graphELList.size() == 1){
            LogicFlowGraphEL graphEL = logicFlow.graphELList.get(0);
            List<Node> flowNodeList = graphEL.getList().keySet().stream().collect(Collectors.toList());
            if(CollUtil.isNotEmpty(graphEL.getPreList())){
                flowNodeList.addAll(graphEL.getPreList());
            }
            if(CollUtil.isNotEmpty(graphEL.getFinallyList())) {
                flowNodeList.addAll(graphEL.getFinallyList());
            }
            return exec(logicFlow.flowExecutor, logicFlow.nodeInfoList,logicFlow.dynamicClassList, flowNodeList, graphEL.getFallbackList(), logicFlow.wrapper.toEL(), param, contextBeanArray);
        }
        List<Node> flowNodeList = new ArrayList<>();
        List<Node> fallbackList = new ArrayList<>();
        for (LogicFlowGraphEL graphEL : logicFlow.graphELList){
            List<Node> list = graphEL.getList().keySet().stream().collect(Collectors.toList());
            flowNodeList.addAll(list);
            if(CommonUtil.collUtil.isNotEmpty(graphEL.getFallbackList())){
                fallbackList.addAll(graphEL.getFallbackList());
            }
            if(CollUtil.isNotEmpty(graphEL.getPreList())) {
                flowNodeList.addAll(graphEL.getPreList());
            }
            if(CollUtil.isNotEmpty(graphEL.getFinallyList())) {
                flowNodeList.addAll(graphEL.getFinallyList());
            }
        }
        return exec(logicFlow.flowExecutor, logicFlow.nodeInfoList,logicFlow.dynamicClassList, flowNodeList, fallbackList, logicFlow.wrapper.toEL(), param, contextBeanArray);
    }

    private String exec(FlowExecutor flowExecutor, List<IvyCmp> list, List<IvyDynamicClass> dynamicClassList, List<Node> flowNodeList, List<Node> fallbackList, String el, Object param, Object... contextBeanArray){
        buildComponentIds(list,dynamicClassList,flowNodeList,fallbackList,el);
        return executor(flowExecutor, el, param, contextBeanArray);
    }

    private LiteflowResponse execResponse(FlowExecutor flowExecutor, List<IvyCmp> list, List<IvyDynamicClass> dynamicClassList, List<Node> flowNodeList, List<Node> fallbackList, String el, Object param, Object... contextBeanArray){
        buildComponentIds(list,dynamicClassList,flowNodeList,fallbackList,el);
        return executorResponse(flowExecutor, el, param, contextBeanArray);
    }

    private String executor(FlowExecutor flowExecutor, String el, Object param, Object... contextBeanArray){
        LiteflowResponse response = executorResponse(flowExecutor, el, param, contextBeanArray);
        return response.getExecuteStepStrWithTime();
    }

    private LiteflowResponse executorResponse(FlowExecutor flowExecutor, String el, Object param, Object... contextBeanArray){
        String chainId = "chain_test_"+ NumberUtil.getNextIndex();
        LiteFlowChainELBuilder.createChain().setChainId(chainId).setChainName(chainId).setEL(el).build();
        if(isFuture){
            Future<LiteflowResponse> future = flowExecutor.execute2Future(chainId, param, contextBeanArray);
            try {
                System.out.println("-----------------------------exec start-----------------------------");
                LiteflowResponse response = future.get();
                System.out.println("-----------------------------exec end-----------------------------");
                return response;
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e.getCause());
            }
        }else{
            return flowExecutor.execute2Resp(chainId, param, contextBeanArray);
        }
    }

    private static void buildComponentIds(List<IvyCmp> list, List<IvyDynamicClass> dynamicClassList, List<Node> flowNodeList, List<Node> fallbackList, String el){
        buildComponentIds(list,dynamicClassList,flowNodeList,fallbackList,getComponentIds(el));
    }

    private static final String pattern = "node\\(\"([^\"]+)\"\\)";

    private static List<String> getComponentIds(String el){
        Pattern r = Pattern.compile(pattern);
        Matcher m = r.matcher(el);
        List<String> list = new ArrayList<>();
        while (m.find()) {
            list.add(m.group(1));
        }
        return list;
    }

    private static void buildComponentIds(List<IvyCmp> list, List<IvyDynamicClass> dynamicClassList, List<Node> flowNodeList, List<Node> fallbackList, List<String> componentIds){
        Set<String> typeMap = Arrays.stream(NodeTypeEnum.values()).map(NodeTypeEnum::getCode).collect(Collectors.toSet());
        Map<String, IvyCmp> cmpMap = list.stream().collect(Collectors.toMap(IvyCmp::getComponentId, m -> m));
        Map<Long, IvyCmp> cmpMaps = list.stream().collect(Collectors.toMap(IvyCmp::getId, m -> m));
        Map<String, String> nodeTypeMap = flowNodeList.stream().collect(Collectors.toMap(m -> m.getProperties().getComponentId(), m->{
            if(typeMap.contains(m.getType())){
                return m.getType();
            }
            return m.getProperties().getType();
        }, (o1,o2)->o2));
        Map<String, NodeProperties> nodeInfoMap = flowNodeList.stream().collect(Collectors.toMap(m -> m.getProperties().getComponentId(), Node::getProperties, (o1, o2)->o2));
        Map<String, IvyDynamicClass> dynamicClassMap = dynamicClassList.stream().collect(Collectors.toMap(m -> m.getClassId(), m -> m));
        Set<String> fallbackSet = new HashSet<>();
        if(fallbackList != null && !fallbackList.isEmpty()){
            for (Node node : fallbackList){
                NodeProperties prop = node.getProperties();
                Long fallbackId = null;
                if(prop.getFallbackCommonId() != null){
                    fallbackId = prop.getFallbackCommonId();
                }else if(prop.getFallbackSwitchId() != null){
                    fallbackId = prop.getFallbackSwitchId();
                }else if(prop.getFallbackIfId() != null){
                    fallbackId = prop.getFallbackIfId();
                }else if(prop.getFallbackForId() != null){
                    fallbackId = prop.getFallbackForId();
                }else if(prop.getFallbackWhileId() != null){
                    fallbackId = prop.getFallbackWhileId();
                }else if(prop.getFallbackBreakId() != null){
                    fallbackId = prop.getFallbackBreakId();
                }else if(prop.getFallbackIteratorId() != null){
                    fallbackId = prop.getFallbackIteratorId();
                }
                IvyCmp nodeInfo = cmpMaps.get(fallbackId);
                if(nodeInfo.getFallbackId() != null){
                    IvyCmp n = cmpMaps.get(nodeInfo.getFallbackId());
                    fallbackSet.add(n.getType());
                    CmpScriptUtil.createNode(n);
                }else{
                    fallbackSet.add(prop.getFallbackType());
                    nodeInfo.setFallbackType(prop.getFallbackType());
                    CmpScriptUtil.createNode(nodeInfo);
                }
            }
        }
        for (String componentId : componentIds){
            NodeProperties nodeInfoWrapper = nodeInfoMap.get(componentId);
            if(nodeInfoWrapper == null){
                IvyCmp nodeInfo = cmpMap.get(componentId);
                IvyDynamicClass dynamicClass = dynamicClassMap.values().stream().filter(m -> componentId.equals(m.getSourceCmpId())).findFirst().orElse(null);
                if(dynamicClass != null){
                    Class<?> cmpClass = buildCmp(dynamicClass);
                    nodeInfo.setClazz(cmpClass.getName());
                    try {
                        if(SpringBeanUtil.getBean(componentId) == null){
                            SpringBeanUtil.registerBean(componentId,cmpClass);
                        }
                    }catch (Exception e){
                        SpringBeanUtil.registerBean(componentId,cmpClass);
                    }

                    String cmpDoOpt = nodeInfo.getCmpDoOpt();
                    if(StrUtil.isNotBlank(cmpDoOpt)){
                        CmpScriptUtil.createNode(cmpMap.get(cmpDoOpt));
                    }

                    CmpScriptUtil.createNode(nodeInfo);
                }
                continue;
            }
            if(nodeInfoWrapper != null && StrUtil.isNotBlank(nodeInfoWrapper.getClazz()) && dynamicClassMap.containsKey(nodeInfoWrapper.getClazz())){
                IvyDynamicClass dynamicClass = dynamicClassMap.get(nodeInfoWrapper.getClazz());
                if(dynamicClass != null){
                    Class<?> cmpClass = buildCmp(dynamicClass);
                    nodeInfoWrapper.setClazz(cmpClass.getName());
                    try {
                        if(SpringBeanUtil.getBean(componentId) == null){
                            SpringBeanUtil.registerBean(componentId,cmpClass);
                        }
                    }catch (Exception e){
                        SpringBeanUtil.registerBean(componentId,cmpClass);
                    }
                    CmpScriptUtil.createNode(nodeInfoWrapper);
                }
                continue;
            }

            if(nodeInfoWrapper != null && StrUtil.isNotBlank(nodeInfoWrapper.getClazz())){
                CmpScriptUtil.createNode(nodeInfoWrapper);
            }

            create(componentId,cmpMap,nodeInfoWrapper,nodeTypeMap,fallbackSet);
        }
    }

    private static final Map<String,Integer> dynamicClassMap = new HashMap<>();

    private static Class<?> buildCmp(IvyDynamicClass item){
        String beanName = item.getSourceClassName();
        dynamicClassMap.putIfAbsent(beanName,item.getVersion());
        // 判断源码是否发生变化
        if(!item.getSourceCode().equals(item.getSourceCode())){ // 不一致
            item.setSourceCode(item.getSourceCode());
            Integer version = dynamicClassMap.get(beanName);
            version = version + 1;
            item.setVersion(version);
            dynamicClassMap.put(beanName, version);
        }
        return compilerCmp(item);
    }

    private static Class<?> compilerCmp(IvyDynamicClass ivyDynamicClass) {
        String root = ClassUtils.getDefaultClassLoader().getResource("").getPath();
        String className = ivyDynamicClass.getSourceClassName()+ivyDynamicClass.getVersion();
        String packagePath = String.join(File.separator,ivyDynamicClass.getPackagePath().split("\\."));
        String rootPath = root+packagePath;
        String forName = ivyDynamicClass.getPackagePath()+"."+className;
        String sourceCode = ivyDynamicClass.getSourceCode();

        Pattern pattern = Pattern.compile("class\\s+(\\w+)");
        Matcher matcher = pattern.matcher(sourceCode);
        sourceCode = matcher.replaceAll("class " + className);

        pattern = Pattern.compile("@LiteflowComponent\\(\"([^\"]+)\"\\)");
        matcher = pattern.matcher(sourceCode);
        sourceCode = matcher.replaceAll("@LiteflowComponent(\"" + className+"\")");
        sourceCode = sourceCode.replaceAll(ivyDynamicClass.getSourceClassName()+".class",className+".class");

        Class<?> dynamicClass = null;
        try {
            // 检查并创建根路径
            File rootDir = new File(rootPath);
            if (!rootDir.exists()) {
                if (!rootDir.mkdirs()) {
                    System.err.println("无法创建动态class目录");
                    System.exit(1);
                }
            }

            String fileClassName = className + ".class";
            String fileJavaName = className + ".java";
            FileUtil.removeFile(rootPath,fileClassName,fileJavaName);

            // 确保输出目录存在
            File outputDir = new File(root);

            // 保存源码到文件
            File sourceFile = new File(rootDir, className + ".java");
            FileWriter writer = new FileWriter(sourceFile);
            writer.write(sourceCode);
            writer.close();

            // 获取系统编译器
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

            // 获取文件管理器
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);

            // 获取编译单元
            Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile));

            // 设置编译参数
            Iterable<String> options = Arrays.asList("-d", outputDir.getAbsolutePath());

            // 创建编译任务
            JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, compilationUnits);

            // 执行编译任务
            boolean success = task.call();

            if (success) {
                // 加载编译后的类
                URLClassLoader classLoader = new URLClassLoader(new URL[]{new File(root).toURI().toURL()});
                dynamicClass = Class.forName(forName, true, classLoader);

                // 创建实例并调用方法
                //Object instance = dynamicHelloClass.getDeclaredConstructor().newInstance();
                //dynamicHelloClass.getMethod(methodName).invoke(instance);
            }

            // 关闭文件管理器
            fileManager.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dynamicClass;
    }

    private static void create(String componentId,Map<String, IvyCmp> cmpMap,IvyCmp nodeInfoWrapper,Map<String, String> nodeTypeMap,Set<String> fallbackSet){
        if(cmpMap.containsKey(componentId)){
            CmpScriptUtil.createNode(nodeInfoWrapper);
        }else{
            try {
                Object bean = SpringBeanUtil.getBean(componentId);
                if(bean == null){
//                    System.out.println("组件不存在："+componentId);
                    create(componentId,nodeInfoWrapper,nodeTypeMap,fallbackSet);
                }
            }catch (Exception e){
                create(componentId,nodeInfoWrapper,nodeTypeMap,fallbackSet);
            }
        }
    }

    private static void create(String componentId,IvyCmp nodeInfoWrapper,Map<String, String> nodeTypeMap,Set<String> fallbackSet){
        String type = nodeTypeMap.get(componentId);
        if(!fallbackSet.contains(type)){
            if(nodeInfoWrapper != null){
                if(StrUtil.isBlank(nodeInfoWrapper.getLanguage())){
                    nodeInfoWrapper.setLanguage("java");
                }
                /*if(StrUtil.isNotBlank(nodeInfoWrapper.getClazz())){
                    SpringBeanUtil.registerBean(componentId,cmpClass);
                    return;
                }*/
                if(StrUtil.isBlank(nodeInfoWrapper.getScript())){
                    switch (nodeInfoWrapper.getType()){
                        case "common" :
                        case "script":
                            nodeInfoWrapper.setType(NodeTypeEnum.SCRIPT.getCode());
                            nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.script.getScriptStr());
                            break;
                        case "switch":
                        case "switch_script":
                            nodeInfoWrapper.setType(NodeTypeEnum.SWITCH_SCRIPT.getCode());
                            nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.switch_script.getScriptStr());
                            break;
                        case "if":
                        case "if_script":
                            nodeInfoWrapper.setType(NodeTypeEnum.IF_SCRIPT.getCode());
                            nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.if_script.getScriptStr());
                            break;
                        case "for":
                        case "for_script":
                            nodeInfoWrapper.setType(NodeTypeEnum.FOR_SCRIPT.getCode());
                            nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.for_script.getScriptStr());
                            break;
                        case "while":
                        case "while_script":
                            nodeInfoWrapper.setType(NodeTypeEnum.WHILE_SCRIPT.getCode());
                            nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.while_script.getScriptStr());
                            break;
                        case "break":
                        case "break_script":
                            nodeInfoWrapper.setType(NodeTypeEnum.BREAK_SCRIPT.getCode());
                            nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.break_script.getScriptStr());
                            break;
                        case "iterator":
                        default: break;
                    }
                }
                CmpScriptUtil.createNode(nodeInfoWrapper);
            }else{
                CmpScriptUtil.createNode(componentId);
            }
        }
    }
}
